#include "raster.h"
#include "raster-fixed.h"
#include "raster-channels.h"

ALWAYS_INLINE Pixel _InterpolatePixel(Raster *src, RasterPosUV *uv, int mode, int testmode, Pixel colorkey)
{
    if(mode == BLIT_INTERPOLATE_BILINEAR)
    {
         /* pixel offsets */
        Pixel *v0 = src->pixels + (src->pitch * ICLAMP(FixedToInt(uv->v), 0, src->height - 1));
        Pixel *v1 = src->pixels + (src->pitch * ICLAMP(FixedToInt(uv->v) + 1, 0, src->height - 1));
        int u0 = ICLAMP(FixedToInt(uv->u), 0, src->width - 1);
        int u1 = ICLAMP(FixedToInt(uv->u) + 1, 0, src->width - 1);
        /* pixel proportions */
        Half uf = FixedToHalf(uv->u & 0xFFFF);
        Half uo = HALF_ONE - uf;
        Half vf = FixedToHalf(uv->v & 0xFFFF);
        Half vo = HALF_ONE - vf;
        /* looping */
        Half mods[4] = { HalfMul(uo, vo), HalfMul(uf, vo), HalfMul(uo, vf), HalfMul(uf, vf) };
        Pixel pels[4] = { *(v0 + u0), *(v0 + u1), *(v1 + u0), *(v1 + u1) };

        if(!(testmode & BILINEAR_TEST_COLORKEY))
        {
            colorkey = 0; /* transparent black */
        }

        return InterpolateBilinear(pels, mods, testmode, colorkey);
    }
    int u = FixedToInt(uv->u + FIXED_HALF);
    int v = FixedToInt(uv->v + FIXED_HALF);
    return *(src->pixels + (v * src->pitch) + u);
}

#define BLITLOOP(code)\
{\
    while(num)\
    {\
        code \
        dp++;\
        num--;\
    }\
}

static void _DirectBlit(Pixel *dp, Pixel *sp, int num, BlitMode *mode)
{
    while(num)
    {
        *dp = *sp;
        dp++;
        sp++;
        num--;
    }
}

static void _AdaptOpaqueBlit(Pixel *dp, Pixel *sp, int num, BlitMode *mode)
{
    while(num)
    {
        *dp = AdaptPixel(*sp, mode);
        dp++;
        sp++;
        num--;
    }
}

static void _AlphaBlit(Pixel *dp, Pixel *sp, int num, BlitMode *mode)
{
    while(num)
    {
        *dp = AlphaBlendPixels(*dp, *sp);
        dp++;
        sp++;
        num--;
    }
}

static void _AdaptBlit(Pixel *dp, Pixel *sp, int num, BlitMode *mode)
{
    while(num)
    {
        Pixel p = AdaptPixel(*sp, mode);
        *dp = AlphaBlendPixels(*dp, p);
        dp++;
        sp++;
        num--;
    }
}

static void _ColorkeyBlit(Pixel *dp, Pixel *sp, int num, BlitMode *mode)
{
    while(num)
    {
        if(*sp != mode->colorkey)
        {
            Pixel p = AdaptPixel(*sp, mode);
            *dp = AlphaBlendPixels(*dp, p);
        }

        dp++;
        sp++;
        num--;
    }
}

#define WOULD_MAKE_TRANSLUSCENT(mode)\
    ((mode->modes & BLIT_ALPHAMOD) && mode->mod.alpha.high < 255) || \
    ((mode->modes & BLIT_COLORMOD) && (mode->mod.color.wht & 0xFF000000) != 0xFF000000)

int __cdecl LineBlitXRGB(Raster *dest, Raster *src, unsigned int num, const RasterPos *dPos, const RasterPos *sPos, BlitMode *mode)
{
    Pixel *dp = dest->pixels + (dPos->y * dest->pitch) + dPos->x;
    Pixel *sp = src->pixels + (sPos->y * src->pitch) + sPos->x;
    if(mode == NULL || mode->modes <= BLIT_OPAQUE)
    {
        _DirectBlit(dp, sp, num, mode);
    }
    else if(mode)
    {
        if(mode->modes & BLIT_COLORKEY)
        {
            _ColorkeyBlit(dp, sp, num, mode);
        }
        else if(!WOULD_MAKE_TRANSLUSCENT(mode))
        {
            _AdaptOpaqueBlit(dp, sp, num, mode);
        }
        else
        {
            _AdaptBlit(dp, sp, num, mode);
        }
    }
    return 0;
}

#define WOULD_MAKE_OPAQUE(mode)\
    ((mode->modes & BLIT_OPAQUE)) || \
    ((mode->modes & BLIT_ALPHAMOD) && (mode->mod.alpha.high == 255) && (mode->mod.alpha.high == 255)) || \
    ((mode->modes & BLIT_COLORMOD) && ((mode->mod.color.wht & 0xFF000000) == 0xFF000000) \
        && ((mode->mod.color.blk & 0xFF000000)))

int __cdecl LineBlitARGB(Raster *dest, Raster *src, unsigned int num, const RasterPos *dPos, const RasterPos *sPos, BlitMode *mode)
{
    Pixel *dp = dest->pixels + (dPos->y * dest->pitch) + dPos->x;
    Pixel *sp = src->pixels + (sPos->y * src->pitch) + sPos->x;
    if(mode == NULL || mode->modes == BLIT_NORMAL)
    {
        _AlphaBlit(dp, sp, num, mode);
    }
    else if(mode)
    {
        if(mode->modes & BLIT_COLORKEY)
        {
            _ColorkeyBlit(dp, sp, num, mode);
        }
        else if(!WOULD_MAKE_OPAQUE(mode))
        {
            _AdaptBlit(dp, sp, num, mode);
        }
        else
        {
            _AdaptOpaqueBlit(dp, sp, num, mode);
        }
    }
    return 0;
}

int __cdecl LineBlitUVXRGB(Raster *dest, Raster *src, unsigned int num, const RasterPos *dPos, const RasterPosUV *leftUV, const RasterPosUV *rightUV, BlitMode *mode)
{
    Pixel *dp = dest->pixels + (dPos->y * dest->pitch) + dPos->x;
    Pixel sp;
    RasterPosUV sPos = *leftUV;
    /* num-1 seems to produce a better result here */
    Fixed invWidth = FIXED_ONE / IMAX(1, (num - 1));
    Fixed uIter = FixedMul(rightUV->u - leftUV->u, invWidth);
    Fixed vIter = FixedMul(rightUV->v - leftUV->v, invWidth);
    int interpol = BLIT_INTERPOLATE_NEAREST;

    int test = 0;
    if(mode)
    {
        if(mode->interpolation <= BLIT_INTERPOLATE_BILINEAR)
            interpol = mode->interpolation;
        if(mode->modes & BLIT_COLORKEY)
            test |= BILINEAR_TEST_COLORKEY;
    }

    if(mode == NULL || mode->modes == BLIT_NORMAL || mode->modes == BLIT_OPAQUE)
    {
        BLITLOOP({
                 sp = _InterpolatePixel(src, &sPos, interpol, test, 0);
                *dp = sp;
                sPos.u += uIter;
                sPos.v += vIter;
                })
    }
    else
    {
        if(mode->modes & BLIT_COLORKEY)
        {
            BLITLOOP({
                 sp = _InterpolatePixel(src, &sPos, interpol, test, mode->colorkey);
                 if(sp != mode->colorkey)
                 {
                     sp = AdaptPixel(sp, mode);
                     *dp = AlphaBlendPixels(*dp, sp);
                 }

                 sPos.u += uIter;
                 sPos.v += vIter;
                 })
        }
        else
           BLITLOOP({
                 sp = _InterpolatePixel(src, &sPos, interpol, test, 0);
                 sp = AdaptPixel(sp, mode);
                 *dp = AlphaBlendPixels(*dp, sp);
                 sPos.u += uIter;
                 sPos.v += vIter;
                 })
    }
    return 0;
}
int __cdecl LineBlitUVARGB(Raster *dest, Raster *src, unsigned int num, const RasterPos *dPos, const RasterPosUV *leftUV, const RasterPosUV *rightUV, BlitMode *mode)
{
    Pixel *dp = dest->pixels + (dPos->y * dest->pitch) + dPos->x;
    Pixel sp;
    RasterPosUV sPos = *leftUV;
    /* num-1 seems to produce a better result here */
    Fixed uIter = (rightUV->u - leftUV->u) / IMAX(1, (num));
    Fixed vIter = (rightUV->v - leftUV->v) / IMAX(1, (num));
    int interpol = BLIT_INTERPOLATE_NEAREST;

    int test = 0;
    if(mode)
    {
        if(mode->interpolation <= BLIT_INTERPOLATE_BILINEAR)
            interpol = mode->interpolation;
        if(mode->modes & BLIT_COLORKEY)
            test |= BILINEAR_TEST_COLORKEY;
    }
    test |= BILINEAR_TEST_ALPHA; /* prevent blackening/whitening of border pixels */

    if(mode == NULL || mode->modes == BLIT_NORMAL)
    {
        BLITLOOP({
                sp = _InterpolatePixel(src, &sPos, interpol, test, 0);
                *dp = AlphaBlendPixels(*dp, sp);
                sPos.u += uIter;
                sPos.v += vIter;
                })
    }
    else
    {
        if(mode->modes & BLIT_COLORKEY)
        {
            BLITLOOP({
                 sp = _InterpolatePixel(src, &sPos, interpol, test, 0);
                 if(sp != mode->colorkey)
                 {
                     sp = AdaptPixel(sp, mode);
                     *dp = AlphaBlendPixels(*dp, sp);
                 }

                 sPos.u += uIter;
                 sPos.v += vIter;
                 })
        }
        else
           BLITLOOP({
                 sp = _InterpolatePixel(src, &sPos, interpol, test, 0);
                 sp = AdaptPixel(sp, mode);
                 *dp = AlphaBlendPixels(*dp, sp);
                 sPos.u += uIter;
                 sPos.v += vIter;
                 })
    }

    return 0;
}

/* since fixed point values should always be positive in blit loops,
    we can save an instruction by forcing an unsigned divide
*/
ALWAYS_INLINE Fixed fxinv(Fixed a)
{
    return ((unsigned)0x80000000 / (unsigned)a) << 1;
}

#define AFFINELOOP(a, b, uv, code)\
{\
while(a != b)\
{\
    code\
    \
    uv.u += du;\
    uv.v += dv;\
    ++dp;\
    ++a;\
}\
}

#define SUBAFFINEFULL(code, freq)\
{\
RasterPosUV uv, uv2;\
Fixed invz = fxinv(span->w0);\
uv.u = FixedMul(span->u0, invz);\
uv.v = FixedMul(span->v0, invz);\
while(x1 - x0 > freq)\
{\
    int i = 0;\
    span->w0 += wStep;\
    span->u0 += uStep;\
    span->v0 += vStep;\
    invz = fxinv(span->w0);\
    uv2.u = FixedMul(span->u0, invz);\
    uv2.v = FixedMul(span->v0, invz);\
    du = (uv2.u - uv.u) / freq;\
    dv = (uv2.v - uv.v) / freq;\
    AFFINELOOP(i, freq, uv, code)\
    uv = uv2;\
    x0 += freq;\
}\
invz = fxinv(span->w1);\
uv2.u = FixedMul(span->u0 + uStep, invz);\
uv2.v = FixedMul(span->v0 + vStep, invz);\
du = (uv2.u - uv.u) / freq;\
dv = (uv2.v - uv.v) / freq;\
AFFINELOOP(x0, x1, uv, code)\
}

#define PERSPECTIVECORRECTLOOP(code)\
while(x0 < x1)\
{\
    Fixed invz = fxinv(span->w0);\
    RasterPosUV uv;\
    uv.u = FixedMul(span->u0, invz);\
    uv.v = FixedMul(span->v0, invz);\
    \
    code\
    \
    dp++;\
    span->u0 += du;\
    span->v0 += dv;\
    span->w0 += dw;\
    x0++;\
}\

int __cdecl SpanBlitXRGB(Raster *dest, Raster *src, int y, RasterSpan *span, BlitMode *mode)
{
    int x0 = FixedToInt(span->x0);
    int x1 = FixedToInt(span->x1);
    Pixel *dp;
    int freq = 16; /* default to sub-affine correction every 16 pixels, results are decent at a distance */

    Fixed du = 0, dv = 0, dw = 0;
    Fixed uStep, vStep, wStep;

    int test = 0;
    int interpol = BLIT_INTERPOLATE_NEAREST;

    if(mode)
    {
        if(mode->interpolation <= BLIT_INTERPOLATE_BILINEAR)
            interpol = mode->interpolation;
        if(mode->modes & BLIT_COLORKEY)
            test |= BILINEAR_TEST_COLORKEY;
        if(mode->perspective_correction >= 0)
            freq = mode->perspective_correction;
    }

    if(x0 != x1)
    {
        Fixed invwidth = FIXED_ONE / (x1-x0);

        du = FixedMul(span->u1 - span->u0, invwidth);
        dv = FixedMul(span->v1 - span->v0, invwidth);
        dw = FixedMul(span->w1 - span->w0, invwidth);
        uStep = du * freq;
        vStep = dv * freq;
        wStep = dw * freq;

        dp = dest->pixels + (y * dest->pitch) + FixedToInt(span->x0);
    }
    else
        return 0;
    if(freq > 1) /* sub-affine correction */
    {
        if(!mode || mode->modes <= BLIT_OPAQUE) /* optimal case */
        {
            SUBAFFINEFULL({
                            *dp = _InterpolatePixel(src, &uv, interpol, test, 0) | 0xFF000000;
                          }, freq)
        }
        else if(mode->modes & BLIT_COLORKEY)
        {
            SUBAFFINEFULL({
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            if(sp != mode->colorkey)
                            {
                                sp = AdaptPixel(sp, mode);
                                *dp = AlphaBlendPixels(*dp, sp);
                            }
                          }, freq)
        }
        else
        {
            SUBAFFINEFULL({
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            sp = AdaptPixel(sp, mode);
                            *dp = AlphaBlendPixels(*dp, sp);
                          }, freq)
        }
    }
    else if(freq == 1)
    {
        if(!mode || mode->modes <= BLIT_OPAQUE) /* optimal case */
        {
            PERSPECTIVECORRECTLOOP({
                            *dp = _InterpolatePixel(src, &uv, interpol, test, 0) | 0xFF000000;
                          })
        }
        else if(mode->modes & BLIT_COLORKEY)
        {
            PERSPECTIVECORRECTLOOP({
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            if(sp != mode->colorkey)
                            {
                                sp = AdaptPixel(sp, mode);
                                *dp = AlphaBlendPixels(*dp, sp);
                            }
                          })
        }
        else
        {
            PERSPECTIVECORRECTLOOP({
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            sp = AdaptPixel(sp, mode);
                            *dp = AlphaBlendPixels(*dp, sp);
                          })
        }
    }
    else
    {
        RasterPosUV uv;
        uv.u = span->u0;
        uv.v = span->v0;
        if(!mode || mode->modes <= BLIT_OPAQUE) /* optimal case */
        {
            AFFINELOOP(x0, x1, uv, {
                            *dp = _InterpolatePixel(src, &uv, interpol, test, 0) | 0xFF000000;
                          })
        }
        else if(mode->modes & BLIT_COLORKEY)
        {
            AFFINELOOP(x0, x1, uv, {
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            if(sp != mode->colorkey)
                            {
                                sp = AdaptPixel(sp, mode);
                                *dp = AlphaBlendPixels(*dp, sp);
                            }
                          })
        }
        else
        {
            AFFINELOOP(x0, x1, uv, {
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            sp = AdaptPixel(sp, mode);
                            *dp = AlphaBlendPixels(*dp, sp);
                          })
        }
    }
    return 0;
}
int __cdecl SpanBlitARGB(Raster *dest, Raster *src, int y, RasterSpan *span, BlitMode *mode)
{
    int x0 = FixedToInt(span->x0);
    int x1 = FixedToInt(span->x1);
    Pixel *dp;
    int freq = 16; /* default to sub-affine correction every 16 pixels, results are decent at a distance */

    Fixed du = 0, dv = 0, dw = 0;
    Fixed uStep, vStep, wStep;

    int test = 0;
    int interpol = BLIT_INTERPOLATE_NEAREST;

    if(mode)
    {
        if(mode->interpolation <= BLIT_INTERPOLATE_BILINEAR)
            interpol = mode->interpolation;
        if(mode->modes & BLIT_COLORKEY)
            test |= BILINEAR_TEST_COLORKEY;
        if(mode->perspective_correction >= 0)
            freq = mode->perspective_correction;
    }

    if(x0 != x1)
    {
        Fixed invwidth = FIXED_ONE / (x1-x0);

        du = FixedMul(span->u1 - span->u0, invwidth);
        dv = FixedMul(span->v1 - span->v0, invwidth);
        dw = FixedMul(span->w1 - span->w0, invwidth);
        uStep = du * freq;
        vStep = dv * freq;
        wStep = dw * freq;

        dp = dest->pixels + (y * dest->pitch) + FixedToInt(span->x0);
    }
    else
        return 0;
    if(freq > 1) /* sub-affine correction */
    {
        if(!mode || mode->modes == BLIT_NORMAL) /* optimal case */
        {
            SUBAFFINEFULL({
                            *dp = AlphaBlendPixels(*dp, _InterpolatePixel(src, &uv, interpol, test, 0));
                          }, freq)
        }
        else if(mode->modes & BLIT_COLORKEY)
        {
            SUBAFFINEFULL({
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            if(sp != mode->colorkey)
                            {
                                sp = AdaptPixel(sp, mode);
                                *dp = AlphaBlendPixels(*dp, sp);
                            }
                          }, freq)
        }
        else
        {
            SUBAFFINEFULL({
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            sp = AdaptPixel(sp, mode);
                            *dp = AlphaBlendPixels(*dp, sp);
                          }, freq)
        }
    }
    else if(freq == 1)
    {
        if(!mode || mode->modes == BLIT_NORMAL) /* optimal case */
        {
            PERSPECTIVECORRECTLOOP({
                            *dp = AlphaBlendPixels(*dp, _InterpolatePixel(src, &uv, interpol, test, 0));
                          })
        }
        else if(mode->modes & BLIT_COLORKEY)
        {
            PERSPECTIVECORRECTLOOP({
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            if(sp != mode->colorkey)
                            {
                                sp = AdaptPixel(sp, mode);
                                *dp = AlphaBlendPixels(*dp, sp);
                            }
                          })
        }
        else
        {
            PERSPECTIVECORRECTLOOP({
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            sp = AdaptPixel(sp, mode);
                            *dp = AlphaBlendPixels(*dp, sp);
                          })
        }
    }
    else
    {
        RasterPosUV uv;
        uv.u = span->u0;
        uv.v = span->v0;
        if(!mode || mode->modes == BLIT_NORMAL) /* optimal case */
        {
            AFFINELOOP(x0, x1, uv, {
                            *dp = AlphaBlendPixels(*dp, _InterpolatePixel(src, &uv, interpol, test, 0));
                          })
        }
        else if(mode->modes & BLIT_COLORKEY)
        {
            AFFINELOOP(x0, x1, uv, {
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            if(sp != mode->colorkey)
                            {
                                sp = AdaptPixel(sp, mode);
                                *dp = AlphaBlendPixels(*dp, sp);
                            }
                          })
        }
        else
        {
            AFFINELOOP(x0, x1, uv, {
                            Pixel sp = _InterpolatePixel(src, &uv, interpol, test, 0);
                            sp = AdaptPixel(sp, mode);
                            *dp = AlphaBlendPixels(*dp, sp);
                          })
        }
    }
    return 0;
}
